package org.lex.reflect.browsing.model;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;


import org.lex.reflect.browsing.util.ClassNameUtilities;
import org.lex.utils.ToString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ClassPathFactory {
	private Logger log = LoggerFactory.getLogger(ClassPathFactory.class);

	public List<ClassPath> loadSystemAndUserClassPaths() {
		List<ClassPath> classPaths = new ArrayList<ClassPath>();
		try {
			List<ClassPath> userClassPaths = new ClassPathFactory().loadUserClassPath();
			classPaths.addAll(userClassPaths);
			log.debug("user classpath count=" + userClassPaths.size());
		} catch (Exception e) {
			log.error("load java.class.path failed", e);
		}
		try {
			List<ClassPath> systemClassPaths = loadSystemClassPath();
			classPaths.addAll(systemClassPaths);
			log.debug("system classpath count=" + systemClassPaths.size());
		} catch (IOException e) {
			log.error("load system classpath failed", e);
		}
		return classPaths;
	}

	/**
	 * load every "java.class.path" classpath item, and add every item as
	 * <code>root</code>'s child
	 * 
	 * @param root
	 * @return
	 */

	public List<ClassPath> loadUserClassPath() {
		String javaClassPath = System.getProperty("java.class.path");
		String[] classPaths = ClassNameUtilities.split(javaClassPath, File.pathSeparator);
		log.debug("java.class.path=\n" + ToString.toString(classPaths, "", "", "\n"));

		List<ClassPath> classPathList = new ArrayList<ClassPath>(classPaths.length);
		for (int i = 0; i < classPaths.length; i++) {
			File file = new File(classPaths[i]);
			ClassPath classPath = new ClassPath(file);
			if (ClassNameUtilities.isArchiveFileName(classPaths[i])) {
				try {
					loadJarClassPath(file, classPath);
				} catch (IOException e) {
					log.error("load jar ClassPath failed: " + file, e);
				}
			} else {
				try {
					loadDirectoryClassPath(file, "", classPath);
				} catch (IOException e) {
					log.error("load directory ClassPath failed: " + file, e);
				}
			}
			classPathList.add(classPath);
		}
		return classPathList;

	}

	public List<ClassPath> loadSystemClassPath() throws IOException {
		List<ClassPath> systemClasspaths = new ArrayList<ClassPath>();

		File[] jars = new File[] { new File(getRTJarFile()) };
		for (File jar : jars) {
			ClassPath cp = new ClassPath(jar);
			loadJarClassPath(new File(getRTJarFile()), cp);
			systemClasspaths.add(cp);
		}
		return systemClasspaths;
	}

	private void loadDirectoryClassPath(File dir, String pkg, ClassPath classPath) throws IOException {
		StringBuilder currentPath = new StringBuilder();
		File[] files = dir.listFiles();
		for (int i = 0; i < files.length; i++) {
			File file = files[i];
			if (true == file.isFile()) {
				String name = file.getName();
				if (-1 == name.indexOf('$') && true == ClassNameUtilities.isClassFileName(name)) {
					currentPath.setLength(0);
					currentPath.append(pkg);
					if (0 != pkg.length()) {
						currentPath.append('.');
					}
					currentPath.append(name);
					classPath.getClassNames().add(ClassNameUtilities.canonicalizeClassName(currentPath.toString()));
				}
			} else if (true == file.isDirectory()) {
				currentPath.setLength(0);
				currentPath.append(pkg);
				if (0 != pkg.length()) {
					currentPath.append('.');
				}
				currentPath.append(file.getName());
				loadDirectoryClassPath(file, currentPath.toString(), classPath);
			}
		}
	}

	private void loadJarClassPath(File jar, ClassPath classPath) throws IOException {
		InputStream in = new FileInputStream(jar);
		ZipInputStream zin = new ZipInputStream(in);
		ZipEntry entry = null;
		String name = null;
		while ((entry = zin.getNextEntry()) != null) {
			name = entry.getName();
			if (ClassNameUtilities.isClassFileName(name)) {
				name = ClassNameUtilities.canonicalizeClassName(name);
				classPath.getClassNames().add(name);
			}
		}
		zin.close();
	}

	private String getRTJarFile() {
		String url = Class.class.getResource("/java/lang/String.class").toExternalForm();
		log.trace("String --> " + url);
		if (!url.startsWith("jar:file:")) {
			return null;
		}
		int i = url.indexOf("!");
		if (i == -1) {
			return null;
		}
		String filePath = url.substring("jar:file:/".length(), i);
		log.trace("rt.jar --> " + filePath);
		return filePath;
	}
}
